(function (global, factory) {
    global._utils = factory()
}(this, function () {
    'use strict';

    const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g
    const MOZ_HACK_REGEXP = /^moz([A-Z])/
    const ieVersion = Number(document.documentMode)

    const utils = {
        extend: function (o, n, override) {
          let obj = JSON.parse(JSON.stringify(o))
          for (let p in n) {
            if (n.hasOwnProperty(p) && (!obj.hasOwnProperty(p) || override)) {
              obj[p] = n[p]
            }
          }
          return obj
        },
        camelCase: function (name) {
          return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
            return offset ? letter.toUpperCase() : letter
          }).replace(MOZ_HACK_REGEXP, 'Moz$1')
        },
        setStyle: function (element, styleName, value) {
          if (!element || !styleName) return;
          if (typeof styleName === 'object') {
            for (var prop in styleName) {
              if (styleName.hasOwnProperty(prop)) {
                this.setStyle(element, prop, styleName[prop]);
              }
            }
          } else {
            if (styleName.slice(0, 2) === '--') {
              element.style[styleName] = value
            } else {
              styleName = utils.camelCase(styleName)
              if (styleName === 'opacity' && ieVersion < 9) {
                element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')';
              } else {
                element.style[styleName] = value;
              }
            }
          }
        },
        getStyle: ieVersion < 9 ? function(element, styleName) {
          if (!element || !styleName) return null;
          styleName = utils.camelCase(styleName);
          if (styleName === 'float') {
            styleName = 'styleFloat';
          }
          try {
            switch (styleName) {
              case 'opacity':
                try {
                  return element.filters.item('alpha').opacity / 100;
                } catch (e) {
                  return 1.0;
                }
              default:
                return (element.style[styleName] || element.currentStyle ? element.currentStyle[styleName] : null);
            }
          } catch (e) {
            return element.style[styleName];
          }
        } : function(element, styleName) {
          if (!element || !styleName) return null;
          styleName = utils.camelCase(styleName);
          if (styleName === 'float') {
            styleName = 'cssFloat';
          }
          try {
            var computed = document.defaultView.getComputedStyle(element, '');
            return element.style[styleName] || computed ? computed[styleName] : null;
          } catch (e) {
            return element.style[styleName];
          }
        },
        getImgInfo: function (src) {
          return new Promise(function(resolve,reject) {
            let img = new Image()
            img.src = src
            img.onload = function (e) {
              resolve({
                width: e.currentTarget.width,
                height: e.currentTarget.height
              })
            }
          })
        },
        isObjectEmpty: function (obj) {
          return Object.keys(obj).length === 0
        }
    }

    const attr = function(element, attrs, value) {
      if (arguments.length === 1) {
        return undefined
      }
      if (arguments.length === 2 && typeof attrs === 'string') {
        if (element) {
          return element.getAttribute(attrs);
        }
        return undefined;
      } else if (arguments.length === 2 && typeof attrs === 'object') {
        for (const key in attrs) {
          if (Object.hasOwnProperty.call(attrs, key)) {
            const item = attrs[key];
            element.setAttribute(key, item);
          }
        }
      }
      if (arguments.length === 3 && typeof attrs === 'string') {
        if (element) {
          element.setAttribute(attrs, value);
        }
      }
      return this;
    }

    const on = (function() {
        if (document.addEventListener) {
          return function(element, event, handler) {
            if (element && event && handler) {
              element.addEventListener(event, handler, false)
            }
          }
        } else if (document.attachEvent) {
          return function(element, event, handler) {
            if (element && event && handler) {
              element.attachEvent('on' + event, handler)
            }
          }
        } else {
          return function(element, event, handler) {
            if (element && event && handler) {
              element['on' + event] = handler
            }
          }
        }
    })()

    const off = (function() {
      if (document.removeEventListener) {
        return function(element, event, handler) {
          if (element && event) {
            element.removeEventListener(event, handler, false);
          }
        };
      } else if (document.detachEvent) {
        return function(element, event, handler) {
          if (element && event) {
            element.detachEvent('on' + event, handler);
          }
        };
      } else {
        return function(element, event, handler) {
          if (element && event && handler) {
            element['on' + event] = false
          }
        };
      }
    })()

    const getArray = (function() {
      if (Array.from) {
        return function(data) {
          return Array.from(data)
        }
      } else {
        return function(data) {
          return Array.prototype.slice.call(data)
        }
      }
    })()

    const scrollElement = function(element, reduceElement) {
      if (!element) return
      let el = document.querySelector(element)
      let elementTop = el.offsetTop
      let elementParent = el.offsetParent
      while (elementParent) {
        elementTop += elementParent.offsetTop;
        elementParent = elementParent.offsetParent;
      }
      if (reduceElement) {
        elementTop = elementTop - document.querySelector(reduceElement).offsetHeight
      }
      document.documentElement.scrollTo({
        top: elementTop,
        behavior: 'smooth'
      })
    }

    const getUrlData = function() {
      let href = window.location.href
      let obj = {}
      if (href.indexOf('?') !== -1) {
        let query = href.substring(href.indexOf('?') + 1);
        let vars = query.split('&')
        for (let i = 0; i < vars.length; i++) {
          let pair = vars[i].split('=')
          obj[pair[0]] = pair[1]
        }
      }
      return obj
    }

    const ActiveOptions = {
        activeClass: 'active',
        toggle: false, // 是否开启toggleClass切换active类名
        hover: false,
        otherActive: { // 是否开启在其他元素上切换active类名
          el: '', // 其他元素的类名
          removeEl: ''
        },
        fun: null, // 切换时要执行的事件
        clickFun: null // 点击切换时要执行的事件
    }

    function Active(selector, userOptions) {
        if (!(this instanceof Active)) return new Active(selector, userOptions)
        this.init(selector, userOptions)
    }

    Active.prototype = {
        constructor: Active,
        options: null,
        ActiveEls: null,
        otherActiveEls: null,
        init: function(selector, userOptions) {
          if (typeof selector !== 'string' || !selector) return
          this.options = utils.extend(ActiveOptions, userOptions, true)
          const _this = this
          const options = this.options
          _this.ActiveEls = getArray(document.querySelectorAll(selector))
          if (options.otherActive.el) {
            if (typeof options.otherActive.el === 'object') {
              _this.otherActiveEls = {}
              options.otherActive.el.forEach(function(item,index) {
                _this.otherActiveEls[index] = getArray(document.querySelectorAll(item))
              })
            } else {
              _this.otherActiveEls = getArray(document.querySelectorAll(options.otherActive.el))
            }
          }
          if (options.otherActive.removeEl) {
            _this.otherActiveEls.forEach(function(item,index) {
              if (Array.isArray(options.otherActive.removeEl)) {
                options.otherActive.removeEl.map(function(child){
                  _this.bindRemoveEl(item, child, index)
                })
              } else {
                _this.bindRemoveEl(item, options.otherActive.removeEl, index)
              }
            })
          }
          let event = options.hover ? 'mouseover' : 'click'
          _this.ActiveEls.forEach(function(item,index) {
            on(item, event, function() {
              _this.selectIndex(index)
              if (options.clickFun) options.clickFun(index)
            })
          })
        },
        selectIndex: function(index) {
          this.switchClass(this.ActiveEls, index)
          if (this.otherActiveEls) {
            if (Array.isArray(this.otherActiveEls)) {
              this.switchClass(this.otherActiveEls, index)
            } else {
              for (const key in this.otherActiveEls) {
                const item = this.otherActiveEls[key]
                this.switchClass(item, index)
              }
            }
          }
          if (this.options.fun) this.options.fun(index)
        },
        switchClass: function (element, i) {
          const _this = this
          element.forEach(function(item, index) {
            if (index == i) {
              _this.addActive(item)
            } else {
              _this.removeActive(item)
            }
          })
        },
        addActive: function(element) {
          if (this.options.toggle) {
            element.classList.toggle(this.options.activeClass)
          } else {
            element.classList.add(this.options.activeClass)
          }
        },
        removeActive: function(element) {
          element.classList.remove(this.options.activeClass)
        },
        bindRemoveEl: function(parent, name, index) {
          const _this = this
          let el = parent.querySelector(name)
          if (el) {
            on(el,'click',function () {
              _this.removeActive(_this.ActiveEls[index])
              _this.removeActive(_this.otherActiveEls[index])
            })
          }
        }
    }

    function incline(selector) {
        if (!(this instanceof incline)) return new incline(selector)
        this.init(selector)
    }

    incline.prototype = {
        els: null,
        init: function (selector) {
            if (typeof selector !== 'string' || !selector) return
            const _this = this
            _this.els = getArray(document.querySelectorAll(selector))
            _this.els.forEach(function (item) {
                on(item, 'mousemove', function (e) {
                    let rotate = 10
                    let rotateX = 0,rotateY = 0
                    let x = e.clientX - item.getBoundingClientRect().left
                    let y = e.clientY - item.getBoundingClientRect().top
                    let w = item.offsetWidth
                    let h = item.offsetHeight
                    let x1 = 0,y1 = 0
                    x1 = x >= (w / 2) ? x - (w / 2) : (w / 2) - x
                    y1 = y >= (h / 2) ? y - (h / 2) : (h / 2) - y
                    x1 = x >= (w / 2) ? x1 : x1 * -1
                    y1 = y <= (h / 2) ? y1 : y1 * -1
                    rotateX = rotate * y1 / (h / 2)
                    rotateY = rotate * x1 / (w / 2)
                    utils.setStyle(item, {
                        'will-change': 'transform',
                        'transform': `translate3d(0px, 0%, 0px) scale3d(1, 1, 1) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(0deg) skew(0deg, 0deg)`,
                        'transform-style': 'preserve-3d'
                    })
                })
                on(item, 'mouseout', function (e) {
                    utils.setStyle(item, {
                        'will-change': 'transform',
                        'transform': `translate3d(0px, 0%, 0px) scale3d(1, 1, 1) rotateX(0deg) rotateY(0deg) rotateZ(0deg) skew(0deg, 0deg)`,
                        'transform-style': 'preserve-3d'
                    })
                })
            })
        }
    }

    function customCursor(selector) {
      if (!(this instanceof customCursor)) return new customCursor(selector)
      this.init(selector)
    }

    customCursor.prototype = {
      init: function (selector) {
        if (typeof selector !== 'string' || !selector) return
        const cursor_container = document.querySelector(selector)
        const cursorEl = document.createElement('span')
        cursorEl.classList.add('custom-cursor')
        const onMouseMove = function (e) {
          let x = e.clientX
          let y = e.clientY
          utils.setStyle(cursorEl, {
            'left': x + 'px',
            'top': y + 'px'
          })
        }
        on(cursor_container, 'mouseenter' , function (e) {
          if (!cursor_container.contains(e.relatedTarget) && e.relatedTarget 
        != cursorEl) {
            cursor_container.parentNode.append(cursorEl)
            utils.setStyle(document.body, {
              'cursor': 'none'
            })
            onMouseMove(e)
            on(document, 'mousemove', onMouseMove)
          }
        })
        on(cursor_container, 'mouseout', function (e) {
          if (!cursor_container.contains(e.relatedTarget) && e.relatedTarget 
        != cursorEl) {
            cursorEl.remove()
            utils.setStyle(document.body, {
              'cursor': 'auto'
            })
            off(document, 'mousemove', onMouseMove)
          }
        })
      }
    }

    function modal() {
      if (!(this instanceof modal)) return new modal()
      this.init()
    }
  
    modal.prototype = {
      modalActive: {},
      init: function() {
        const _this = this
        let modalButtons = getArray(document.querySelectorAll('[modal-button]'))
        if (modalButtons.length === 0) return
        modalButtons.forEach(function(item){
          on(item, 'click', _this.handleModal.bind(_this))
        })
      },
      handleModal: function (e) {
        let name = attr(e.currentTarget, 'modal-id')
        let modal = document.getElementById(name)
        if (this.modalActive[name]) {
          delete this.modalActive[name]
          utils.setStyle(modal,'display', 'none')
          if (utils.isObjectEmpty(this.modalActive)) {
            utils.setStyle(document.body, 'overflow', 'initial')
          }
        } else {
          utils.setStyle(modal, 'display', 'block')
          if (utils.isObjectEmpty(this.modalActive)) {
            utils.setStyle(document.body, 'overflow', 'hidden')
          }
          this.modalActive[name] = modal
        }
      }
    }

    return {
      Active,
      incline,
      customCursor,
      modal,
      scrollElement,
      on,
      getUrlData
    }
}))